
import logging
import sys
import types
import random

from google.appengine.ext import db
from google.appengine.api import taskqueue
from google.appengine.ext.db.metadata import Kind

from impact import platform

import pickle
import base64

WORK_QUEUES 	= ['default']
PRIORITY_QUEUES = ['default']

config	    = None
def init(app_config):
	global config
	global WORK_QUEUES
	global PRIORITY_QUEUES

	config 			= app_config['queue']
	WORK_QUEUES 	= config['work_queues']
	PRIORITY_QUEUES = config['priority_queues']

def get_url_mappings():
	return [
		('/internal/api/queue/(.*)', QueueApi)
		]

class QueueApi(platform.ServiceHandler):

	def __init__(self):
		#logging.info('dash api init')
		pass

	def page_apply(self,uctx,args,params):
		payload_params = pickle.loads(base64.b64decode(params['payload']))
		queue_name = self.request.headers.get('X-AppEngine-QueueName')
		#logging.info("!!! PAGEAPPLY TASK QUEUE IS "+queue_name)
		kind 	 			  = payload_params['kind']
		page_size 			  = payload_params['page_size']
		ancestor  		 	  = payload_params['ancestor']
		filters   		 	  = payload_params['filters']
		callback_module  	  = payload_params['callback_module']
		callback_f       	  = payload_params['callback_f']
		callback_params  	  = payload_params['callback_params']
		callback_auto_expand  = payload_params['callback_auto_expand']
		multiplex             = payload_params['multiplex']
		work_queue            = payload_params.get('work_queue')
		if not multiplex:
			work_queue = work_queue or random.choice(WORK_QUEUES)
			payload_params['work_queue'] = work_queue

		#optional params
		cursor 					= payload_params.get('cursor')
		callback_page_size 		= payload_params.get('callback_page_size') or page_size

		q = db.Query(db.class_for_kind(kind),keys_only = True)
		if ancestor:
			q.ancestor(db.Key(encoded=ancestor))
		if cursor:
			q.with_cursor(cursor)
		for f in filters:
			v = f[1]
			if isinstance(v,basestring):
				if v.startswith('_refkey_'):
					v = db.Key(encoded=f[1][8:])
			q.filter(f[0],v)

		results = q.fetch(page_size)

		callback_chunks = [map(lambda x: str(x),results[i:i+callback_page_size]) for i in range(0, len(results), callback_page_size)]

		for callback_chunk in callback_chunks:
			if multiplex:
				w_queue_name = random.choice(WORK_QUEUES)
			else:
				w_queue_name = work_queue

			dispatch_params = {'callback_module':callback_module,
							   'callback_f':callback_f,
							   'callback_params':callback_params,
							   'callback_auto_expand':callback_auto_expand,
							   'callback_keys':callback_chunk}
			taskqueue.add(url='/internal/api/queue/dispatch_page_apply_callback', params={'payload':base64.b64encode(pickle.dumps(dispatch_params))},queue_name=w_queue_name)

		if len(results) < page_size:
			return

		#continue query
		cursor = q.cursor()
		payload_params['cursor'] = cursor
		if multiplex:
			queue_name = _next_priority_queue()

		taskqueue.add(url='/internal/api/queue/page_apply', params={'payload':base64.b64encode(pickle.dumps(payload_params))},queue_name=queue_name)

	def dispatch_page_apply_callback(self,uctx,args,params):
		payload_params 	     = pickle.loads(base64.b64decode(params['payload']))
		queue_name = self.request.headers.get('X-AppEngine-QueueName')
		logging.info("!!! DISPATCH PAGE APPLY TASK QUEUE IS "+queue_name)

		callback_module      = payload_params['callback_module']
		callback_f 			 = payload_params['callback_f']
		callback_params 	 = payload_params['callback_params']
		callback_auto_expand = payload_params['callback_auto_expand']
		callback_args		 = payload_params['callback_keys']

		callback_entities 	 = [db.Key(k) for k in callback_args]

		if len(callback_entities) > 0 and callback_auto_expand:
			callback_entities = db.get(callback_entities)

		if not isinstance(callback_entities,list):
			callback_entities = [callback_entities]

		module 		= sys.modules[callback_module]
		f 			= getattr(module,callback_f)
		put,delete 	= f(entities=callback_entities,params=callback_params)

		#def txn():
		if put != None:
			db.put(put)
		if delete != None:
			db.delete(delete)
		#if put != None or delete != None:
		#	db.run_in_transaction(txn)

	def execute_deferred(self,uctx,args,params):
		payload_params 	     = pickle.loads(base64.b64decode(params['payload']))
		queue_name = self.request.headers.get('X-AppEngine-QueueName')
		#logging.info("!!! EXECUTE DEFER TASK QUEUE IS "+queue_name)
		callback_module      = payload_params['callback_module']
		callback_f 			 = payload_params['callback_f']
		callback_params 	 = payload_params['callback_params']

		module = sys.modules[callback_module]
		f = getattr(module,callback_f)
		f(params=callback_params)

	def test(self,uctx,args,params):
		page_apply("UserProfile",testing,callback_page_size=1)
		return 'ok'

	def insert_test(self,uctx,args,params):
		queue(insert_test, {'num':5,'total':100})

	def delete_test(self,uctx,args,params):
		page_apply(Test,page_apply_delete,page_size=8,callback_page_size=3)

def page_apply_delete(entities,params):
	#logging.info('deleteing '+str(len(entities))+" "+str(entities))
	return None,entities

class Test(platform.ImpactModel):
	str = db.StringProperty()

def insert_test(params):

	num   = params['num']
	current_num = params.get('current_num') or 0
	total = params['total']
	logging.info("!!! INSERT  current_num: "+str(current_num)+" total:"+str(total))
	if current_num >= total:
		return
	ii = []
	for i in range(num):
		ii.append(Test())
	db.put(ii)
	params['current_num'] = current_num+num
	queue(insert_test,params)

def testing(entities,params):
	logging.info("TESTING 1 2 3")
	logging.info("ENTITIES IS "+str(entities))
	logging.info("PARAMS IS "+str(params))
	entities = db.get(entities)
	for e in entities:
		logging.info(e.email)
	return entities,None




def page_apply(kind,callback,
			   callback_params		= None,
			   callback_page_size	= None,
			   callback_auto_expand = False,
			   ancestor		= None,
			   filters		= None,
			   page_size	= 10,
			   multiplex    = True):

		callback_module,callback_name = _get_callback_info(callback)

		if callback_params == None:
			callback_params = {}
		if filters == None:
			filters = []

		if kind == None:
			raise platform.ImpactException("Must provide a kind.")
		if isinstance(kind,Kind):
			kind = kind.kind_name
		else:
			kind = kind.__name__

		if isinstance(ancestor,db.Model):
			ancestor = ancestor.key()

		if ancestor is not None:
			ancestor = str(ancestor)

		for filter in filters:
			v = filter[1]
			if isinstance(v,db.Model):
				filter[1] = "_refkey_"+str(v.key())
			elif isinstance(v,db.Key):
				filter[1] = "_refkey_"+str(v)

		#caller = inspect.currentframe().f_back
		#logging.info("Called from module"+str(caller.f_globals['__name__'])+" "+caller.f_code.co_name)
		#if isinstance(callback,types.FunctionType):
		#	callback = callback.__name__

		payload_params 			    		 	 = {}
		payload_params['kind']      		 	 = kind
		payload_params['filters']   		 	 = filters
		payload_params['ancestor']  		 	 = ancestor
		payload_params['page_size'] 		 	 = page_size
		payload_params['callback_module']    	 = callback_module
		payload_params['callback_f']  	     	 = callback_name
		payload_params['callback_params']    	 = callback_params
		payload_params['callback_auto_expand']	 = callback_auto_expand
		payload_params['callback_page_size']	 = callback_page_size
		payload_params['multiplex']	 			 = multiplex

		query_queue = _next_priority_queue()
		taskqueue.add(url='/internal/api/queue/page_apply', params={'payload':base64.b64encode(pickle.dumps(payload_params))},queue_name=query_queue)

PAGE_APPLY_QUEUE_IDX = 0
def _next_priority_queue():
	global PAGE_APPLY_QUEUE_IDX
	PAGE_APPLY_QUEUE_IDX += 1
	return PRIORITY_QUEUES[PAGE_APPLY_QUEUE_IDX % len(PRIORITY_QUEUES)]


PRIORITY_NORMAL = 0x01
PRIORITY_HI 	= 0x02
def	queue(callback,callback_params=None,priority=PRIORITY_HI):
		callback_module,callback_name = _get_callback_info(callback)

		if callback_params == None:
			callback_params = {}
		if priority == PRIORITY_HI:
			queue_name = _next_priority_queue()
		else:
			queue_name = random.choice(WORK_QUEUES)

		payload_params = {}
		payload_params['callback_module']    	 = callback_module
		payload_params['callback_f']  	     	 = callback_name
		payload_params['callback_params']    	 = callback_params
		taskqueue.add(url='/internal/api/queue/execute_deferred', params={'payload':base64.b64encode(pickle.dumps(payload_params))},queue_name=queue_name)


def _get_callback_info(callback):
		if not isinstance(callback,types.FunctionType):
			raise platform.ImpactException("Callback "+callback.__name__+" is not valid. Must be a module level method.")
		callback_module = callback.func_globals['__name__']
		callback_name   = callback.__name__
		return callback_module,callback_name
